KURZUS: Programozás alapjai

MODUL: III. modul

10. lecke: Mutatók 2/2

| Ebben a leckében folytatjuk a mutatókkal kapcsolatos tudnivalók megismerését. Készítünk többdimenziós tömböket, megnézzük, hogyan adhatjuk ezeket át függvényeknek, és hogyan kezelhetjük őket mutatók segítségével. A kódmutatók használatáról is ejtünk szót. Közben olyan területekre is kitérünk, mint a véletlenszámok generálása, a dinamikus memóriakezelés, a parancssori paraméterek és környezeti változók használata. Elmélyítjük a típusdefiníciókkal kapcsolatos ismereteinket, hogy összetett, mutatókkal kapcsolatos típusokat áttekinthetőbben tudjunk definiálni.
A lecke végére a hallgatónak tisztában kell lennie
A többdimenziós tömbök deklarálásának, definiálásának, inicializálásának módjával. A véletlenszám generálás mikéntjével. A dinamikus memóriakezelés módjával. Hogyan kell tömböket függvénynek átadni, azt felhasználni, visszaadni. A parancssori paraméterek és környezeti változók tartalmának olvasásával. A programleállítás különféle lehetőségeivel. Kódmutatók és azok tömbjei készítésével, használatával. A typedef használatával.
A lecke kulcsfogalmai: többdimenziós tömb, véletlenszám generátor, elsődleges adatterület, verem, heap, dinamikus memóriakezelés, parancssori paraméter, környezeti változó, kilépési állapot, kódmutató.
Többdimenziós tömbök
A többdimenziós tömböket a tömb típus tömbjeiként konstruálja meg a fordító. A deklaráció
típus azonosító[<méret1>][méret2]. . .[méretN] <={inicializátorlista}>;
alakú. Az elhelyezésről egyelőre annyit, hogy sorfolytonos, azaz a jobbra álló index változik gyorsabban. Például a
    double matrix[2][3];
sorfolytonosan a következő sorrendben helyezkedik el a tárban:
    matrix[0][0], matrix[0][1], matrix[0][2], matrix[1][0], matrix[1][1], matrix[1][2]
Egy elem elérése például
azonosító[index1][index2]. . .[indexN]
módon megy, ahol az indexekre igazak a következők:
0 <= index1 < méret1 0 <= index2 < méret2 . . . 0 <= indexN < méretN
A tömbök inicializálásáról korábban mondottak most is érvényben vannak, de a többdimenziós tömb további tömbökből áll, s így az inicializálási szabályok is rekurzívan alkalmazandók. Az inicializátorlista egymásba ágyazott, egymástól vesszővel elválasztott inicializátorok és inicializátorlisták sorozata a sorfolytonos elhelyezési rend betartásával. Például a 4x3-as t tömb első sorának minden eleme 1 értékű, második sorának minden eleme 2 értékű, s így tovább.
    int t[4][3]={{1, 1, 1}, {2, 2, 2}, {3, 3, 3}, {4, 4, 4}};
Ha az inicializátorlistában nincs beágyazott inicializátorlista, akkor az ott felsorolt értékek az alaggregátumok, s azon belül az elemek sorrendjében veszik fel az aggregátum elemei. Az előző példával azonos eredményre vezetne a következő inicializálás is:
    int t[4][3] = {1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4};
Kapcsos zárójelek akár az egyes inicializátorok köré is tehetők, de ha a fordítót nem kívánjuk "becsapni", akkor célszerű őket az aggregátum szerkezetét pontosan követve használni! Az
    int t[4][3]={{1, 1, 1}, {2, 2, 2}, {3, 3, 3}};
hatására a t mátrix utolsó sorára (a t[3] tömbre) nem jut inicializátor, így a t[3][0], t[3][1] és t[3][2] elemek mind zérus kezdőértéket kapnak. Az
    int t[4][3]={{1}, {2}, {3}};
eredményeként a t[0][0] 1, a t[1][0] 2, a t[2][0] 3 lesz, és a tömb összes többi eleme zérus.
Tudjuk, hogyha nem adjuk meg, akkor az inicializátorlista elemszámából állapítja meg a tömbméretet a fordító. Többdimenziós tömb deklarációjában ez azonban csak az első méretre vonatkozik, a további méreteket mind kötelező előírni.
    int a[][]={{1, 1}, {2, 2}, {3, 3}};           /* HIBÁS */
    int t[][3]={{1, 1, 1}, {2, 2, 2}, {3, 3, 3}}; /* OK */
Folytassuk tovább a pelda23.c- ben megkezdett magyar kártyás példánkat! Újabb programunknak legyen az a feladata, hogy megkeveri a paklit, s kioszt öt lapot! Aztán újra kioszt öt lapot, s így tovább mindaddig, míg a kártya el nem fogy. Újabb keverés következik és újabb osztások. A szoftvernek akkor van vége, ha már nem kérnek több lapot.
Azt, hogy milyen kártyákat osztott már ki a pakliból, statikus élettartamú, csak a kartya.c forrásfájlban elérhető, kétdimenziós, SZINDB* KTYDB-s, kty tömbben tartja nyilván a program. A megfelelő tömbelem zérus, ha még pakliban van a lap, és 1-gyé válik, ha kiosztják. A statikus, a kartya.c forrásmodulra ugyancsak lokális, ktydb változóban a pakli aktuális kártyaszámát őrzi a szoftver.
A következő sorokkal mindig a kartya.c bővítendő!
    static int kty[SZINDB][KTYDB];
    static int ktydb=SZINDB*KTYDB;
Az UjPakli függvény ugyanezt az állapotot állítja elő.
    void UjPakli(void){
      int i, j;
      for(i=0; i<SZINDB; ++i)
        for(j=0; j<KTYDB; ++j) kty[i][j]=0;
      ktydb=SZINDB*KTYDB; }
A Mennyi rutinnal lekérdezhető, hogy még hány kártya van a pakliban.
    int Mennyi(void){
      return ktydb; }
Az int UjLap(void) függvény egy lapot ad a pakliból, azonban ezt véletlenszerűen teszi a következő technikával:
Ha nincs kártya a pakliban, -1-et szolgáltat. Ha egyetlen lapból áll a pakli, akkor azt adja. Ha ktydb<KTYDB, akkor előállít egy 1 és ktydb közti véletlenszámot. Végigjárja a paklit, és visszaadja a véletlenszámadik, még nem kiosztott kártyát. Ha ktydb>=KTYDB, akkor véletlen színt és véletlen kártyát választ. Kiadja a lapot, ha még eddig nem osztotta ki. Ha a kártya már nincs a pakliban, újat választ, és így tovább.
Látszik, hogy szükségünk lesz véletlenszám generátorra!
Véletlenszám generátor
Használatához az stdlib.h fejfájlt kell bekapcsolni. Egész számok pszeudóvéletlen sorozatát generálja közel egyenletes eloszlással 0 és RAND_MAX között az
int rand(void);
függvény. A rutinnak nincs hibás visszatérése. A véletlenszám generálás kezdőértékét lehet beállítani a
void srand(unsigned int kezd);
függvénnyel. Az alapértelmezett induló érték 1, így ilyen értékű kezd paraméterrel mindig újrainicializáltathatjuk a véletlenszám generátort, azaz a rand hívások ugyanazt a véletlenszám sorozatot produkálják srand(1) után, mintha mindenféle srand megidézés nélkül használtuk volna a rand-ot.
Másképp fogalmazva, a rand nem véletlen módon, hanem nagyon is determinisztikusan ad vissza hívásonként egy-egy számot egy véges sorozat egymást követő tagjai közül. Az srand azt szabályozza, hogy melyik tagtól kezdjük el felhasználni a rendelkezésre álló számokat. Ha a sorozat tagjai elfogytak, akkor a rand elölről kezdve újra felhasználja a számokat. Nyilvánvaló, hogy a rand által visszaadott értékek csak akkor fognak véletlenszerűnek hatni, ha az srand is véletlenszerű paramétert kap, lehetőleg a program futása során egyetlen alkalommal, az első rand hívást megelőzően.
Az srand-ot a véletlenszerű indulást is biztosítandó rendszerint a time.h-ban helyet foglaló
time_t time(time_t *timer);
függvény kezd paraméterrel szokták meghívni. A time rutin az aktuális rendszer időt 1970. január elseje éjfél óta eltelt másodpercek számában, time_t (long) típusban szolgáltatja. Nincs hibás visszatérés. A visszatérési értéket a timer címen is elhelyezi, ha a paraméter nem NULL mutató. NULL mutató aktuális paraméter esetén viszont nem tesz ilyet. Az srand függvény szokásos hívása tehát:
    srand(time(NULL))
Véletlenszám generátorral kockadobás értéket a következőképp produkálhatunk:
    rand()%6 + 1
Ha a fejlesztő rendszerben nincs lebegőpontos véletlenszámot generáló függvény, akkor 0 és 1 közötti véletlenszámokat a következő kifejezéssel állíthatunk elő:
    (double)rand()/RAND_MAX
Folytassuk az UjLap függvényt!
    int UjLap(void){
      int i, j, db;
      if(ktydb==SZINDB*KTYDB) srand(time(NULL));
      if(ktydb){
        if(ktydb>=KTYDB)
          while(kty[i=rand()%SZINDB][j=rand()%KTYDB]);
        else{
          db=ktydb>1?rand()%ktydb+1:1;
          for(i=0; i<SZINDB; ++i){
            for(j=0; db&&j<KTYDB; ++j)
              if(!kty[i][j]&&!(--db)) break;
            if(!db) break; } }
        --ktydb;
        kty[i][j]=1;
        return i*OSZTO+j; }
      else return (-1); }
Elkészültünk a kibővített kartya.c-vel, s most írjuk meg a működtető pelda24.c programot!
    /* PELDA24.C: Ot lap osztasa. */
    #include <stdio.h>
    #include <ctype.h>
    #include "kartya.h"
    #define LAPDB 5
    void main(void) {
      int i, c;
      printf("Zsugazas: %d lap leosztasa:\n", LAPDB);
      while(printf("Ossza mar (I/N)? "),
            (c=toupper(getchar()))!='N') {
        if(c=='I') {
          putchar('\n');
          if(Mennyi()<LAPDB) UjPakli();
          for(i=0; i<LAPDB; ++i)
            printf("%-15s", KartyaNev(UjLap()));
          printf("\n\n"); }
        while(c!=EOF&&c!='\n') c=getchar(); } }
Dinamikus memóriakezelés
A C a memóriát három részre osztja. Az elsődleges adatterületen helyezi el a fordító a konstansokat és a statikus objektumokat. A lokális objektumokat és a függvényparamétereket a verembe (stack) teszi. A harmadik memóriaterületet - nevezzük heap-nek, bár más névvel is szokták illetni - futás közben éri el a C program, és változó méretű memória blokkok dinamikus allokálására való. Például fák, listák, tömbök vagy bármi más helyezhető el rajta. Az ismertetett, ANSI szabványos függvények prototípusai az stdlib.h fejfájlban találhatók.
void *calloc(size_t tetelek, size_t meret);
A calloc tetelek*meret méretű memória blokkot foglal, feltölti 0x00-val és visszaadja kezdőcímét. Tulajdonképpen tetelek elemszámú tömbnek foglal helyet, ahol egy elem mérete meret bájt.
Ha nincs elég hely, vagy a tetelek*meret szorzat értéke zérus, NULL mutatót kapunk vissza.
void *malloc(size_t meret);
A malloc legalább meret bájtos memória blokkot foglal, nem tölti fel semmivel és visszaadja kezdőcímét. A blokk nagyobb lehet meret bájtnál a tárillesztéshez igényelt, plusz terület és a karbantartási információ elhelyezése miatt.
Ha nincs elég hely a heap-en, NULL mutatót kapunk vissza a függvénytől. Ha a meret 0, a malloc zérusméretű blokkot allokál, és érvényes mutatót ad vissza erre a területre.
A calloc-kal, a malloc-kal lefoglalt, vagy a rögtön ismertetendő realloc-kal újrafoglalt terület tárillesztése olyan, hogy bármilyen típusú objektum elhelyezésére alkalmas. A függvényektől visszakapott cím explicit típusmódosító szerkezettel bármilyen típusúvá átalakítható.
Tegyük fel, hogy a program futása közben derül ki egy double tömb mérete! Ezt az értéket az int típusú, N változó tartalmazza. Hogyan lehet létrehozni, kezelni, s végül felszabadítani egy ilyen tömböt?
    /* . . . */
    int N;
    double *dtomb;
    /* . . . */
    /* Itt kiderül, hogy mennyi N. */
    /* . . . */
    /* Ettől kezdve szükség van a tömbre. */
    if((dtomb=(double *)malloc(N*sizeof(double)))!=NULL){
      /* Sikeres a memóriafoglalás, azaz használható a tömb.
         Például a 6. eleme dtomb[5] módon is elérhető. */
      /* . . . */
      /* Nincs szükség a továbbiakban a tömbre. */
      free(dtomb);
      /* . . . */ }
    else /* Hibakezelés. */
    /* . . . */
void *realloc(void *blokk, size_t meret);
A realloc meret méretűre szűkíti vagy bővíti a korábban malloc, calloc vagy realloc hívással allokált memória blokkot, melynek kezdőcímét megkapja a blokk paraméterben. Sikeres esetben visszaadja az átméretezett memória blokk kezdőcímét. Ez a cím nem feltétlenül egyezik meg a blokk paraméterben átadottal. Címeltérés esetén a függvény a korábbi memória blokk tartalmát átmozgatja az újba. Az esetleges rövidüléstől eltekintve elmondható, hogy az új blokk megőrzi a régi tartalmát.
Ha az újraallokálás sikertelen memória hiány miatt, ugyancsak NULL mutatót kapunk, de az eredeti blokk változatlan marad.
void free(void *blokk);
A free deallokálja (felszabadítja) a korábban malloc, calloc vagy realloc hívással allokált memóriaterületet, melynek kezdőcímét megkapja a blokk paraméterben. A felszabadított bájtok száma egyezik az allokációkor (vagy a realloc esetén) igényelttel. Ha a blokk NULL, a mutatót elhagyja a free, és rögtön visszatér.
Az érvénytelen mutatós (nem calloc, malloc, vagy realloc függvénnyel foglalt memória terület címének átadása) felszabadítási kísérlet befolyásolhatja a rákövetkező allokációs kéréseket, és fatális hibát is okozhat.
A többdimenziós tömbök belső szerkezetének megértéséhez hozzunk létre dinamikusan egy tömböt, a példában most egy mátrixot!
    /* PELDA25.C: Ketdimenzios tomb letrehozasa dinamikusan. */
    #include <stdio.h>
    #include <stdlib.h>
    typedef long double TIPUS;
    typedef TIPUS **OBJEKTUM;
    int m=3, n=5; /* Sorok es oszlopok szama. */
    int main(void) {
      OBJEKTUM matrix;
      int i, j;
      printf("%d*%d-s, ketdimenzios tomb letrehozasa "
             "dinamikusan.\n", m, n);
      /* A sorok letrehozasa: */
      if(!(matrix=(OBJEKTUM)calloc(m, sizeof(TIPUS *)))) {
        printf("Letrehozhatatlanok a matrix sorai!\n");
        return 1; }
      /* Az oszlopok letrehozasa: */
      for(i = 0; i < m; ++i)
        if(!(matrix[i]=(TIPUS *)malloc(n*sizeof(TIPUS)))) {
          printf("Letrehozhatatlan a matrix %d. sora!\n", i);
          while(--i>=0) free(matrix[i]);
          free(matrix);
          return 1; }
      /* Mesterseges inicializalas: */
      for(i = 0; i < m; ++i)
        for(j = 0; j < n; ++j)
          matrix[i][j] = rand();
      /* Kiiras: */
      printf("A matrix tartalma:\n");
      for(i = 0; i < m; ++i) {
        for(j = 0; j < n; ++j)
          printf("%10.0Lf", matrix[i][j]);
        printf("\n"); }
      /* Az oszlopok felszabaditasa: */
      for(i = m-1; i >= 0; --i) free(matrix[i]);
      /* Sorok felszabaditasa: */
      free(matrix);
      return 0; }
Vegyük észre, hogy a main-nek van visszaadott értéke, mely zérus, ha minden rendben megy, és 1, ha memóriafoglalási probléma lép fel! Figyeljük meg azt is, hogy a memória felszabadítása foglalásával éppen ellenkező sorrendben történik, hogy a heap-en ne maradjanak foglalt "szigetek"! A heap C konstrukció, s ha a program befejeződik, akkor maga is felszabadul, megszűnik létezni.
Összesítve: A matrix azonosító a tömb kezdetére mutató, változó mutató. A tömb kezdete viszont m változó mutatóból álló mutatótömb. A mutatótömb egy-egy eleme n elemű, long double típusú tömb kezdetére mutat. A példa a részek memóriabeli elhelyezkedését is szemlélteti, azaz:
előbb az m elemű mutatótömböt allokáljuk, aztán a mátrix első sora (0-s indexű) n elemének foglalunk helyet. A mátrix második sora (1-s indexű) n elemének helyfoglalása következik. . . . Legvégül a mátrix utolsó (m-1 indexű) sorának n elemét helyezzük el a memóriában.
32 bites címeket feltételezve, 1000-ről indulva, m=3 és n=5 esetén, a matrix a következőképpen helyezkedhet el a memóriában:
Háromdimenziós tömböt úgy valósíthatunk meg, hogy létrehozunk előbb egy mutatótömböt, melynek mindenegyes eleme egy, az előzőekben ismertetett szerkezetű mátrixra mutat.
Néhány szó még a többszörösen alkalmazott index operátorról! A kifejezés1[kifejezés2][kifejezés3]...-ban az index operátorok balról jobbra kötnek, így a fordító először a legbaloldalibb kifejezés1[kifejezés2] kifejezést értékeli ki. A született mutató értékhez aztán hozzáadva kifejezés3 értékét új mutató kifejezést képez, s ezt a folyamatot a legjobboldalibb index kifejezés összegzéséig végzi. Ha a végső mutató érték nem tömb típust címez, akkor az indirekció művelete következik. Tehát például:
    matrix[2][3]  (*(matrix+2))[3]  *(*(matrix+2)+3)
A fordító hasonló módszerrel dolgozik, de az általa létrehozott mátrixszal kapcsolatban az összes mutató - ideértve a mátrix azonosítóját is - konstans.
A többdimenziós tömbök többféleképpen is szemlélhetők. A fordító által létrehozott mátrix (ú.n. statikus tömb) példájánál maradva:
    long double matrix[m][n];
A matrix egy m elemű vektor (tömb) azonosítója. E vektor minden eleme egy n elemű, long double típusú vektor. A matrix[i] (i = 0, 1, ..., m-1) az i-edik, n long double elemű vektor azonosítója. A matrix[i][j] (i = 0, 1, ..., m-1 és j = 0, 1, ..., n-1) a mátrix egy long double típusú eleme.
A dolgokat más oldalról tekintve!
A matrix konstans mutató, mely a mátrix kezdőcímét - tehát matrix[0], n darab long double elemből álló tömb kezdőcímét - tartalmazza. A matrix+i a matrix[i], n darab long double elemű tömb címe. A matrix+i matrix+i+1-től épp n darab long double típusú változó méretével tér el. A matrix+i cím tartalma ugye *(matrix+i) vagy matrix[i]. A matrix[i] tehát az i-edik, n darab long double elemből álló tömb azonosítója: konstans mutató, mely az i-edik, n long double elemű tömb kezdetére mutat. A matrix[i]+j vagy *(matrix+i)+j az i-edik, n long double elemű tömb j-edik elemének címe. E cím tartalma elérhető a következő hivatkozásokkal: matrix[i][j], *(matrix[i]+j) vagy *(*(matrix+i)+j). A &matrix[0][0], a matrix[0], a &matrix[0] és a matrix ugyanaz az érték, azaz a mátrix kezdetének címe, de a matrix[0][0] egy long double azonosítója, a matrix[0] egy n long double elemű tömb azonosítója, és a matrix a tömbökből álló tömb azonosítója. Így: &matrix[0][0] + 1 &matrix[0][1], matrix[0] + 1 *matrix + 1 &matrix[0][1], &matrix[0] + 1 matrix + 1 és matrix + 1 &matrix[1] &matrix[1][0].
Többdimenziós, statikus tömbök esetén a fordító nem generál mutatótömböket a memóriába, csak a puszta tömbelemeket helyezi el a tárban a dinamikus tömbnél tárgyalt módon és sorrendben. A kód azonban előállít konstans mutatóként minden, a dinamikus tömbnél megszokott mutatóhivatkozást. E számításokba a fordító fixen beépíti a többdimenziós tömb méreteit (az elsőtől eltekintve). Az indexelés is módosul kicsit ebben az értelemben. Például a matrix[i][j] elérése a következő:
*((long double *)matrix + i*n + j)
Leegyszerűsítve: A fordító a többdimenziós, statikus tömböt egy akkora egydimenziós tömbben helyezi el, melyben annak minden eleme elfér. A többdimenziós tömb méretei, és az indexek segítségével ez után meghatározza, hogy melyik egydimenziós tömbelemet jelenti a többdimenziós tömbhivatkozás.
Tömbök mint függvényparaméterek
Ha van egy
    float vektor[100];
tömbünk, és kezdőcímével meghívjuk az
    Fv(vektor)
függvényt, akkor a függvény definíciójának
    void Fv(float *v) { /* . . . */ }
vagy
    void Fv(float v[]){ /* . . . */ }
módon kell kinéznie. (Az utóbbi alakról tudjuk, hogy a fordító rögtön és automatikusan átkonvertálja az előző (a mutatós) formára.)
Ne feledjük, hogy ugyan a vektor konstans mutató a hívó függvényben, de v (címmásolat) már változó mutató a meghívott függvényben. Vele tehát elvégezhető például a v++ művelet. A *v, a *(v+i) vagy a v[i] balérték alkalmazásával a vektor tömb bármelyik eleme módosítható a meghívott függvényben.
Emlékezzünk arra is, hogy a meghívott függvénynek is tudnia kell valahonnét a tömb méretét! Például úgy, hogy a méretet is átadjuk paraméterként neki. Az itt elmondottak többdimenziós tömbök vonatkozásában is igazak legalább is az első méretre, de ott már nem teszünk említést erről!
Ha van egy
    float matrix[10][20];
definíciónk, és az azonosítóval meghívjuk
    Fvm(matrix)
módon az Fvm függvényt, akkor hogyan kell az Fvm definíciójának kinéznie? A
    void Fvm(float **m) { /* . . . */ }
próbálkozás rossz, mert a formális paraméter float mutatót címző mutató. A
    void Fvm(float *m[20]) { /* . . . */ }
változat sem jó, mert így a formális paraméter 20 elemű, float típusú objektumokra mutató mutatótömb. Ennél a kísérletnél az az igazi probléma, hogy a [] operátor prioritása nagyobb, mint *-é. Nekünk formális paraméterként 20 float elemű tömbre mutató mutatót kéne átadni. Tehát a helyes megoldás:
    void Fvm(float (*m)[20]) { /* . . . */ }
vagy a "tradicionális" módszer szerint:
    void Fvm(float m[][20]) { /* . . . */ }
amiből rögtön és automatikusan előállítja a fordító az előző (a mutatós) alakot.
Meg kell még említenünk, hogyha a többdimenziós tömböt dinamikusan hozzuk létre, akkor az előzőleg ajánlott megoldás nyilvánvalóan helytelen. A mátrix "horgonypontját" ebben az esetben
    float **matrix;
módon definiáljuk, ami ugye float mutatóra mutató mutató. Tehát ilyenkor a
    Fvmd(matrix)
módon hívott Fvmd függvény helyes formális paramétere:
    void Fvmd(float **m) { /* . . . */ }
Parancssori paraméterek
Minden C programban kell lennie egy a programot elindító függvénynek, mely konzol bázisú alkalmazások esetében a main függvény.
Most és itt csak a main paramétereivel és visszatérési értékével szeretnénk foglalkozni! A paraméterekről állíthatjuk, hogy:
elhagyhatóak és nem ANSI szabványosak.
A main legáltalánosabb alakja:
    int main(int argc, char *argv[]);
A paraméterek azonosítói bizonyos, C nyelvet támogató környezetekben ettől el is térhetnek, de funkciójuk akkor is változatlan marad.
Az argc a main-nek átadott parancssori paraméterek száma, melyben az indított végrehajtandó program azonosítója is benne van, és értéke így legalább 1.
Az argv a paraméter karakterláncokra mutató mutatótömb, ahol az egyes elemek rendre:
argv[0]: A futó program (meghajtónévvel és) úttal ellátott azonosítójára mutató mutató. argv[1]: Az első parancssori paraméter karakterláncára mutató mutató. argv[2]: A második paraméter karakterlánc kezdőcíme. . . . argv[argc - 1]: Az utolsó parancssori paraméter karakterláncára mutató mutató. argv[argc]: NULL mutató.
A main lehetséges alakjai a következők:
    void main(void);
    int main(void);
    int main(int argc);
    int main(int argc, char *argv[]);
Vegyünk egy példát!
    /* PELDA26.C: Parancssori parameterek. */
    #include <stdio.h>
    #include <stdlib.h>
    int main(int argc,char *argv[]) {
      int i;
      printf("Parancssori parameterek:\n");
      printf("Argc erteke %d.\n", argc);
      printf("Az atadott parancssori parameterek:\n");
      for(i=0; i<argc; ++i)
        printf("\targv[%d]: %s\n", i, *argv++);
      return 0; }
Tételezzük fel, hogy a programot a következő parancssorral indítottuk:
    pelda26 elso_par "sodik par" 3 4 stop!
Ekkor a megjelenő kimenet a következő lehet:
    Parancssori paraméterek:
    Argc értéke 6.
    Az átadott parancssori paraméterek:
    argv[0]: C:\C\pelda26.exe
    argv[1]: elso_par
    argv[2]: sodik par
    argv[3]: 3
    argv[4]: 4
    argv[5]: stop!
Beszéljünk kicsit a printf utolsó, *argv++ kifejezéséről! Az argv-t a main paraméterként kapja, tehát csak címmásolat, vagyis a main-ben akár el is rontható. Az argv típusa char **, és funkcionálisan a parancssori paraméter karakterláncok kezdőcímeit tartalmazó mutatótömb kezdetének címe. A rajta végrehajtott indirekcióval a típus char * lesz, s épp a mutatótömb első elemét (argv[0]) érjük el. Az utótag ++ operátor miatt eközben az argv már a második mutatótömb elemre (argv[1]) mutat. Elérjük ezt is, és mellékhatásként az argv megint előbbre mutat egy tömbelemmel. Tehát a ciklusban rendre végigjárjuk az összes parancssori paramétert.
Jusson eszünkbe, hogy a main-nek átadott parancssor maximális hosszát korlátozhatja az operációs rendszer!
A legtöbb operációs rendszerben léteznek
változó=érték
alakú, ún. környezeti változók, melyek definiálják a környezetet (információt szolgáltatnak) az operációs rendszer és a benne futó programok számára. Például a PATH környezeti változó szokta tartalmazni az alapértelmezett keresési utakat a végrehajtható programokhoz, a parancsinterpreter helyét írja elő a COMSPEC, és így tovább. Az operációs rendszer természetesen lehetőséget biztosít ilyen környezeti változók törlésére, megadására, és értékük módosítására.
A felhasználó pl. Windows 10 operációs rendszeren a Parancssor-t elindítva, a következő parancsokkal tudja manipulálni a környezeti változókat:
Parancs | Tevékenység | Példa
set | Listázza az összes környezeti változót, értékével együtt. | set
set kvnev | Csak kvnev változó értékének megjelenítése. | set JANI
set kvnev=ertek | Létező kvnev változó értékének megváltoztatása ertek-re, vagy új változó létrehozása. | set JANI=kovacs
set kvnev= | kvnev környezeti változó törlése. | set JANI=
Arra azonban ügyelni kell, hogy minden parancsértelmező a környezeti változóknak egy önálló készletével rendelkezik, azaz az egyik ilyen készlet változása nem befolyásolja a másikat. Magyarán, ha pl. a szoftverfejlesztő környezet indítja el a programunkat, akkor hiába gépelünk be új környezeti változókat egy parancssorban, abból a programunk valószínűleg semmit sem vesz majd észre.
A környezeti változók értékeit lekérdezni pl. az ANSI szabványos, nem kis-nagybetű érzékeny
char *getenv(const char *valtozo);
függvénnyel lehet, mely visszaadja az aktuális környezet alapján a valtozo nevű környezeti változó értékére mutató mutatót. Ha nincs ilyen változó az aktuális környezeti táblában, NULL mutatót kapunk. A visszakapott nem NULL mutatóval azonban nem célszerű és nem biztonságos dolog a környezeti változó értékét módosítani. Ehhez a nem szabványos putenv rutin használata ajánlott.
A környezeti változó nevének a végére nem kell kitenni az = jelet, azaz például a PATH környezeti változót a getenv("PATH") hívással kérdezhetjük le!
Ha expliciten nem deklaráljuk void-nak, akkor a main-nek int típusú státuszkóddal kell visszatérnie az őt indító programhoz (process), rendszerint az operációs rendszerhez. Konvenció szerint a zérus visszaadott érték (EXIT_SUCCESS) hibátlan futást, s a nem zérus státuszkód (EXIT_FAILURE 1) valamilyen hibát jelez. Magát a main-ből való visszatérést (mondjuk 1-es státuszkóddal) megoldhatjuk a következő módok egyikével:
    return 1;
    exit(1);
Foglalkozzunk kicsit a programbefejezéssel is!
Programbefejezés
A return 1 csak a main-ben kiadva fejezi be a program futását. Az stdlib.h bekapcsolásakor rendelkezésre álló, mindegyik operációs rendszerben használható
void exit(int statusz); void abort(void);
függvények mind befejezik annak a programnak a futását, amiben meghívják őket akármilyen mély rutin szintről is. A statusz paraméter értékét visszakapja a befejezettet indító (várakozó szülő) program, mint kilépési állapotot (exit status). A statusz értéket átveszi persze az operációs rendszer is, ha ő volt a befejezett program indítója.
Az exit függvény a program befejezése előtt meghív minden regisztrált (lásd atexit) kilépési függvényt, kiüríti a kimeneti puffereket, és lezárja a nyitott fájlokat.
Az abort alapértelmezés szerint befejezi az aktuális programot. Megjelenteti például az
    Abnormal program termination
üzenetet az stderr-en, és aztán SIGABRT (abnormális programbefejezés) jelet generál. Ha nem írtak kezelőt (signal) a SIGABRT számára, akkor az alapértelmezett tevékenység szerint az abort 3-as státuszkóddal visszaadja a vezérlést a szülő programnak. Szóval nem üríti a puffereket, és nem hív meg semmilyen kilépési függvényt (atexit) sem.
Függvény (kód) mutatók
A mutatók függvények ún. belépési pontjának címét is tartalmazhatják, s ilyenkor függvény vagy kódmutatókról beszélünk.
Ha van egy
int fv(double, int);
prototípusú függvényünk, akkor erre mutató mutatót
int (*pfv)(double, int);
módon deklarálhatunk. A pfv azonosító ezek után olyan változót deklarál, melyben egy double, s egy int paramétert fogadó és int-tel visszatérő függvények címeit tarthatjuk. A pfv tehát változó kódmutató. Kódmutató konstans is létezik azonban, s ez a függvénynév (a példában az fv).
Vigyázzunk a deklarációban a függvénymutató körüli kerek zárójel pár el nem hagyhatóságára, mert az
int *pfv(double, int);
olyan pfv azonosítójú függvényt deklarál, mely egy double, s egy int paramétert fogad, és int típusú objektumot címző mutatóval tér vissza. A probléma az, hogy a mutatóképző operátor (*) prioritása alacsonyabb a függvényképzőénél (()).
Hogyan lehet értéket adni a függvénymutatónak?
Természetesen a szokásos módokon, azaz hozzárendeléssel:
    pfv = fv;
illetve a deklarációban inicializátor alkalmazásával:
    int fv(double, int);
    int (*pfv)(double, int) = fv;
Vigyázzunk nagyon a típussal, mert az most "bonyolódott"! Csak olyan függvény címe tehető be a pfv-be, mely a függvénymutatóval egyező típusú, azaz int-et ad vissza, egy double és egy int paramétert fogad ebben a sorrendben. A
    void (*mfv)();
szerint az mfv meg nem határozott számú és típusú paramétert fogadó olyan függvényre mutató mutató, melynek nincs visszatérési értéke.
Vegyük észre, hogy a kérdéses függvények definíciója előtt függvénymutatók inicializálására is használható a megadott függvény prototípus!
Hogyan hívhatjuk meg azt a függvényt, melynek címét a kódmutató tartalmazza?
Alkalmaznunk kell a mutatókra vonatkozó ökölszabályunkat, ami azt mondja ki, hogy ahol állhat azonosító a kifejezésben, ott állhat (*mutatóazonosító) is. Vegyük elő újra az előző példát! Ha
    int a = fv(0.65, 8);
az fv függvény hívása, és valamilyen módon lezajlott a pfv = fv hozzárendelés is, akkor az
    a = (*pfv)(0.65, 8);
ugyanaz a függvényhívás.
Itt a pfv-re alkalmaztuk az indirekció operátort (*), de mivel ennek prioritása alacsonyabb a függvényhívás operátorénál (()), ezért a *pfv-t külön zárójelbe kellett tenni!
A kódmutatóval kapcsolatos alapismeretek letárgyalása után feltétlenül ismertetni kell a C fordító függvényekkel kapcsolatos fontos viselkedését: implicit konverzióját!
Ha a kifejezés típussal visszatérő függvény típusú, akkor hacsak nem cím operátor (&) mögött áll, típussal visszatérő függvénymutató típusúvá konvertálja automatikusan és azonnal a fordító.
Ez az implicit konverzió mindenek előtt megvalósul a
utótag-kifejezés(<kifejezéslista>)
függvényhívásban, ahol az utótag-kifejezésnek kell típussal visszatérő függvénycímmé kiértékelhetőnek lennie. A típus a függvényhívás értékének típusa. A dolog praktikusan azt jelenti, hogy a függvény bármilyen függvényre mutató kifejezéssel meghívható.
Milyen műveletek végezhetők a kódmutatókkal?
Képezhető a címük. sizeof operátor operandusai lehetnek. Végrehajtható rajtuk az indirekció művelete is, mint láttuk. Értéket kaphatnak, ahogyan azt az előzőekben ismertettük. Meghívhatók velük függvények. Ezt is áttekintettük. Átadhatók paraméterként függvényeknek. Kódmutatótömbök is létrehozhatók. Függvény visszaadott értéke is lehet. Explicit típuskonverzióval más típusú függvénymutatókká alakíthatók.
Kódmutatókra azonban nem alkalmazható a mutatóaritmetika az egyenlőségi reláció operátoroktól (== és !=) eltekintve.
Foglalkozzunk a kódmutató paraméterrel!
A függvényekre érvényes implicit típuskonverzió a függvényparaméterekre is vonatkozik. Ha a paraméter típussal visszatérő függvény, akkor a fordító automatikusan és rögtön típus típusú értéket szolgáltató függvényre mutató mutatóvá alakítja át.
A következő, kissé elvonatkoztatott példában kitűnően megszemlélhető a kódmutató paraméter függvény prototípusban, ill. függvény aktuális és formális paramétereként.
    /* . . . */
    long Emel(int);
    long Lep(int);
    long Letesz(int);
    void Munka(int n, long (* fv)(int));
    /* . . . */
    void main(void){
      int valaszt=1, n;
      /* . . . */
      switch(valaszt){
        case 1: Munka(n, Emel); break;
        case 2: Munka(n, Lep); break;
        case 3: Munka(n, Letesz); }
      /* . . . */ }
    void Munka(int n, long (* fv)(int)){
      int i;
      long j;
      for(i=j=0; i<n; ++i) j+=(*fv)(i); }
A kódmutató típusa szerint az ilyen függvény egy int paramétert fogad, és long értéket szolgáltat.
A szabványos függvénykönyvtár több olyan függvényt is tartalmaz, amely kódmutató paramétert vár. A korábban már általunk is megvalósított bináris keresés algoritmusa az stdlib.h fejfájl után elérhető:
void *bsearch(const void *kulcs, const void *bazis, size_t elemszam, size_t meret, int (*hasonlito)(const void *, const void *));
Ez a függvény megkeresi a kulcs címen tárolt adatot abban az elemszam elemből álló tömbben, amely a bazis címen helyezkedik el a tárban, és minden eleme meret méretű. A függvény utolsó paramétere egy olyan függvényt címez, mely két mutatót fogad, és int értékkel tér vissza. A mutatók a tömb két tetszőleges elemét címezhetik. A visszatérési érték pozitív, ha az első címen lévő érték nagyobb a második címen lévőnél, negatív, ha a második címen lévő érték nagyobb az első címen lévőnél, és zérus, ha a címzett helyeken lévő értékek egyformák. (Ebben a tulajdonságában tehát emlékeztet a karaktermutatóknál ismertetett strcmp függvényre.) A hasonlito függvényt nekünk kell megírnunk, mivel a bsearch-nek a const void* típusú mutatók miatt semmiféle fogalma nincs arról, hogy a tömb milyen jellegű adatokat tárol, és azokat hogyan kell értelmezni, összehasonlítani. Ez egyébként szándékos, és lehetővé teszi, hogy a függvényt típusfüggetlenül, a feladatok lehető legszélesebb körének megoldására használjuk. A bsearch visszatérési értéke a fellelt elem helye, vagy NULL mutató, ha a keresett elem nincs a tömbben.
Mint azt korábban megtárgyaltuk, bináris keresés csak rendezett tömbön hajtható végre. Szerencsére egy nagyon hatékony, ú.n. gyorsrendező (quick sort) algoritmus is megvalósításra került a szabvány könyvtárakban, így az sem okozhat gondot, ha egy tömb kezdetben rendezetlen. A
void qsort(void *bazis, size_t elemszam, size_t meret, int (*hasonlito)(const void *, const void *));
paramétereinek értelmezése pontosan megegyezik a bsearch-nél elmondottakkal. A qsort nem ad vissza értéket, a bazis címen kezdődő tömböt helyben rendezi.
A fentiek kipróbálására hozzunk létre egy olyan programot, ami egy egészekből álló, kezdetben rendezetlen tömbben keres meg értékeket!
    /* PELDA27.C: rendezes es kereses */
    #include<stdio.h>
    #include<stdlib.h> /* bsearch, qsort miatt */
    #define N 5 /* A tomb elemszama. */
    /* Osszehasonlitja a tomb ket elemet. */
    int hasonlit(const void* a, const void* b) {
      return *((const int*)a) - *((const int*)b); }
    void kiir(const int* t, int n) {
      int i;
      for(i=0; i<n; i++)
        printf("%d%s", t[i], i==n-1?".\n":", "); }
    void keres(int k, const int* t, int n) {
      int* p = (int*)bsearch(&k, t, n, sizeof(int), hasonlit);
      if(p) printf("%d indexe: %d.\n", k, (int)(p-t));
      else printf("%d nincs a tombben.\n", k); }
    void main(void) {
      int szamok[N] = { -3, 54, -29, 12, 47 };
      printf("Szamok rendezes elott:\n");
      kiir(szamok, N);
      qsort(szamok, N, sizeof(int), hasonlit);
      printf("Szamok rendezes utan:\n");
      kiir(szamok, N);
      keres(12, szamok, N);
      keres(1234, szamok, N); }
A program futtatásának eredménye:
    Szamok rendezes elott:
    -3, 54, -29, 12, 47.
    Szamok rendezes utan:
    -29, -3, 12, 47, 54.
    12 indexe: 2.
    1234 nincs a tombben.
Azt mondottuk, hogy kódmutatók tömbökben is elhelyezhetők. Visszatérve az első pfv-s példánkhoz, az
    int (*pfvt[])(double, int) = {fv1, fv2, fv3, fv4, fv5};
deklarációval létrehoztunk egy pfvt azonosítójú, olyan ötelemű tömböt, mely int-et visszaadó, egy double, és egy int paramétert fogadó függvények címeit tartalmazhatja. Feltéve, hogy fv1, fv2, fv3, fv4 és fv5 ilyen prototípusú függvények, a pfvt tömb elemeit kezdőértékkel is elláttuk ebben a deklarációban.
Hívjuk még meg, mondjuk, a tömb 3. elemét!
    a = (*pfvt[2])(0.65, 8);
Alakítsuk át függvénymutató tömböt használóvá a kódmutató paraméternél ismertetett példát!
Az új megoldásunk main-en kívüli része változatlan, a main viszont:
    void main(void){
      int valaszt=1, n;
      long (*fvmt[])(int) = {Emel, Lep, Letesz};
      /* . . . */
      Munka(n, fvmt[valaszt]);
      /* . . . */ }
A kódmutató visszatérési értékhez elemezzük ki a következő függvény prototípust!
void (*signal(int jel, void (* kezelo)(int)))(int);
A signal első paramétere int, a második (void (* kezelo)(int)) viszont értéket vissza nem adó, egy int paramétert fogadó függvénymutató típusú. Kitűnően látszik ezek után, hogy a visszatérési érték void (*)(int), azaz értéket nem szolgáltató, egy int paramétert fogadó függvénymutató. A visszaadott érték típusa tehát a signal második paraméterének típusával egyezik.
A signal.h fejfájlban elhelyezett prototípusú signal függvénnyel különben a program végrehajtása során bekövetkező, váratlan eseményeket (megszakítás, kivétel, hiba stb.), ún. jeleket vagy jelzéseket lehet lekezeltetni. Többféle típusú jel létezik. A void (*)(int) típusú kezelőfüggvényt a manipulálni kívánt jelre külön meg kell írni. A signal rutinnal hozzárendeltetjük a kezelőt (2. paraméter) az első paraméter típusú jelhez, és a signal az eddigi kezelő címével tér vissza.
Egy bizonyos típusú függvénymutató explicit típuskonverzióval
(típusnév) előtag-kifejezés
átalakítható más típusú kódmutatóvá. Ha az e módon átkonvertált mutatóval függvényt hívunk, akkor a hatás a programfejlesztő rendszertől, a hardvertől függ. Viszont, ha visszakonvertáljuk az átalakított mutatót az eredeti típusra, akkor az eredmény azonos lesz a kiindulási függvénymutatóval.
Szedjük csak megint elő az
    int fv(double, int), a;
    int (*pfv)(double, int) = fv;
példánkat, és legyen a
    void (*vpfv)(double);
    vpfv=(void (*)(double))pfv;
A
    (*vpfv)(0.65);
eredményessége eléggé megkérdőjelezhető, de a
    pfv=(int (*)(double, int))vpfv;
után teljesen rendben lesz a dolog:
    a=(*pfv)(0.65, 8);
Emlékezzünk csak! Explicit típusmódosított kifejezés nem lehet balérték.
Foglalkozzunk csak újra egy kicsit a típusnevekkel!
Típusnév
Explicit típusmódosításban, függvénydeklarátorban a paramétertípus rögzítésekor, sizeof operandusaként stb. szükség lehet a típus nevének megadására. Ehhez kell a típusnév, mely szintaktikailag a kérdéses típusú objektum olyan deklarációja, melyből elhagyták az objektum azonosítóját.
típusnév: típusspecifikátor-lista<absztrakt-deklarátor>
absztrakt-deklarátor: mutató <mutató><direkt-absztrakt-deklarátor>
direkt-absztrakt-deklarátor: (absztrakt-deklarátor) <direkt-absztrakt-deklarátor>[<konstans-kifejezés>] <direkt-absztrakt-deklarátor>(<paraméter-típus-lista>)
Az absztrakt-deklarátorban mindig lokalizálható az a hely, ahol az azonosítónak kéne lennie, ha a konstrukció deklaráción belüli deklarátor lenne. Lássunk néhány konkrét példát!
    int *
int típusú objektumra mutató mutató.
    int **
int típusú objektumot címző mutatóra mutató mutató.
    int *[]
Nem meghatározott elemszámú, int típusú mutatótömb.
    int *()
Ismeretlen paraméterlistájú, int-re mutató mutatóval visszatérő függvény.
    int (*[])(int)
int típussal visszatérő, egy int paraméteres, meghatározatlan elemszámú függvénymutató tömb.
    int (*(*())[])(void)
Ismeretlen paraméterezésű, int típussal visszatérő függvénymutatókból képzett, meghatározatlan méretű tömbre mutató mutatót szolgáltató, paraméterrel nem rendelkező függvény.
A problémán: a sok zárójelen, a nehezen érthetőségen typedef alkalmazásával lehet segíteni.
Típusdefiníció (typedef)
Az elemi típusdefinícióról szó volt már a Típusok és konstansok lecke végén. Az ott elmondottakat nem ismételjük meg, viszont annyit újra szeretnénk tisztázni, hogy a típusdefiníció nem vezet be új típust, csak más módon megadott típusok szinonimáit állítja elő. A typedef név, ami egy azonosító, szintaktikailag egyenértékű a típust leíró kulcsszavakkal, vagy típusnévvel.
A típusdefiníció "bonyolításához" először azt említjük meg, hogy a
typedef típus azonosító;
szerkezetben az azonosító a prioritás sorrendjében lehet:
azonosító(): függvény típust képző, utótag operátor. Például: typedef double dfvdi(double, int); dfvdi hatvany; ahol a hatvany egy double, s egy int paramétert fogadó és double-t visszaadó függvény azonosítója. azonosító[]: tömb típust képző, utótag operátor. Például: typedef double dtomb[20]; dtomb t; ahol a t 20 double elemből álló tömb azonosítója. *azonosító: mutató típust képző, előtag operátor. Például: typedef short *shptr; shptr sptr; ahol az sptr short típusú objektumra mutató mutató azonosítója. Ezek az operátorok egyszerre is előfordulhatnak az azonosító-val. Például: typedef int *itb[10]; itb tomb; ahol a tomb 10 elemű int objektumokra mutató mutatótömb azonosítója.
Megemlítendő még, hogy az előzőek alkalmazásával "csínján" kell bánni, hisz az így típusdefiniált azonosítóknak éppen a jellege (függvény, tömb, mutató) veszik el.
A típusdefiníció további komplexitása abból fakad, hogy a
typedef típus azonosító;
szerkezetbeli típus korábbi typedef típus azonosító;-ban definiált azonosító is lehet. Tehát a típusdefinícióval létrehozott típuspecifikátor típus lehet egy másik típusdefinícióban.
Nézzünk néhány példát!
    typedef int *iptr;
    typedef char nev[30];
    typedef enum {no, ferfi, egyeb} sex;
    typedef long double *ldptr;
    ldptr ptr2; /* long double objektumra mutató mutató.*/
    ldptr fv2(nev); /* 30 elemű karaktertömb paramétert fogadó,
                   long double objektumra mutató mutatóval
                   visszatérő függvény. */
    typedef iptr (*ipfvi)(sex);
    ipfvi fvp1; /* int-re mutató mutatót visszaadó, egy
                   sex típusú enum paramétert fogadó
                   függvényre mutató mutató. */
    typedef ipfvi ptomb[5];
    ptomb tomb; /* 5 elemű, int-re mutató mutatót
                   szolgáltató, egy sex típusú paramétert
                   fogadó függvényre mutató mutatótömb. */
    iptr fugg(ptomb); /*int-re mutató mutatót visszaadó, 5
                   elemű, int-re mutató mutatóval
                   visszatérő, egy sex enum paraméteres
                   függvényre mutató mutatótömböt
                   paraméterként fogadó függvény. */
A típusdefinícióval a program típusai parametrizálhatók, azaz a program portábilisabb lesz, hisz az egyetlen typedef módosításával a típus megváltoztatható. A komplex típusokra megadott typedef nevek ezen kívül javítják a program olvashatóságát is.
Lokális szinten megadott típusdefiníció lokális hatáskörű is. Az általánosan használt típusdefiníciókat globálisan, a feladathoz tartozó fejfájlban szokták előírni.
Egy utolsó kérdés: Mikor ekvivalens két típus?
Ha a két típusspecifikátor-lista egyezik, beleértve azt is, hogy ugyanaz a típusspecifikátor többféleképpen is megadható. Például: a long, a long int és a signed long int azonosak. Ha az absztrakt-deklarátoraik a typedef típusok kifejtése, és bármely függvényparaméter azonosító törlése után ekvivalens típusspecifikátor-listákat eredményeznek.
A típusekvivalencia meghatározásánál a tömbméretek és a függvényparaméter típusok is lényegesek.

| Feladatok
| 1. Kezdő programozó olyan programot szeretett volna írni, ami abécé sorrendbe rendezve megjeleníti a szabvány kimeneten a szavak tömbben tárolt szavakat.
    /*01*/#include <stdio.h>
    /*02*/#include <stdlib.h>
    /*03*/#include <string.h>
    /*04*/#define DB 5
    /*05*/int hasonlit(const void *p1, const void *p2) {
    /*06*/  return strcmp((const char*)p1, (const char*)p2); }
    /*07*/int main(void) {
    /*08*/  int i;
    /*09*/  char szavak[DB] = {"egy", "ket", "har", "negy", "ot"};
    /*10*/  qsort(szavak, DB, sizeof(char*), hasonlit);
    /*11*/  for(i=0; i<DB; i++) printf("%s\n", szavak[i]);
    /*12*/  return 0; }
| Jelölje meg azokat a sorokat, amelyekkel a hibás program működésre bírható!
| /*06*/ return strcmp((const char*)p2, (const char*)p1); }
| /*06*/ return strcmp(*(const char**)p1, *(const char**)p2); }
| /*07*/void main(void) {
| /*09*/ char* szavak[DB] = {"egy", "ket", "har", "negy", "ot"};
| /*09*/ char (*szavak)[DB] = {"egy", "ket", "har", "negy", "ot"};
| 2. Egyszeri programozó szeretett volna egy olyan függvényt írni, ami véletlenszerű, [min, max] intervallumba eső egész értékekkel tölti fel a paraméterként kapott, statikusan allokált mtx tömb első sor sorát és oszlop oszlopát.
    /*1*/#include <stdlib.h>
    /*2*/#define MAX 10
    /*3*/void mtxgen(int *mtx[MAX], int sor, int oszlop,
    /*4*/            int min, int max) {
    /*5*/  int i, j;
    /*6*/  for(i=0; i<sor; i++)
    /*7*/    for(j=0; j<oszlop; j++)
    /*8*/      mtx[i][j] = min + rand()%(max-min+1); }
| Jelölje meg azokat a programsorokat, amelyekkel a hibás program helyesen fog működni!
| /*3*/void mtxgen(int (*mtx)[MAX], int sor, int oszlop,
| /*3*/void mtxgen(int *(mtx)[], int sor, int oszlop,
| /*3*/void mtxgen(int **mtx, int sor, int oszlop,
| /*8*/ mtx[i][j] = min + rand()%(max-min); }
| /*8*/ mtx[i][j] = min + rand()%(max-min) + 1; }
| /*8*/ mtx[i][j] = min + rand()/(max-min+1); }
| 3. Kezdő programozónk szándéka az volt, hogy 10 db. valós, -2 és +2 közé eső, véletlen számot jelenítsen meg a szabvány kimeneten.
    /*01*/#include <stdio.h>
    /*02*/#include <stdlib.h>
    /*03*/#include <time.h>
    /*04*/#define MIN -2.
    /*05*/#define MAX 2.
    /*06*/void main(void) {
    /*07*/  int i;
    /*08*/  srand(time(NULL));
    /*09*/  for(i=0; i<10; i++) {
    /*10*/    srand(time(NULL));
    /*11*/    printf("%.3f ", MIN + (rand()/RAND_MAX)*(MAX-MIN)); } }
| Jelölje meg azokat a programsorokat, amelyekkel programjának működése helyessé tehető!
| /*08*/ /* srand(time(NULL)); */
| /*10*/ /* srand(time(NULL)); */
| /*11*/ printf("%.3f ", MIN + rand()/RAND_MAX*(MAX-MIN)); } }
| /*11*/ printf("%.3f ", MIN + ((double)rand()/RAND_MAX)*(MAX-MIN)); } }
| /*11*/ printf("%.3f ", MIN + ((double)rand()/RAND_MAX)*(MAX-MIN+1.)); } }
| /*11*/ printf("%.3f ", MIN + (rand()/RAND_MAX)%(MAX-MIN)); } }
| 4. Készítsen programot, mely a JANI, fordítási időben változtatható azonosítójú, környezeti változóról megállapítja, hogy létezik-e! Ha létezik, akkor eldönti, hogy értéke "lo", "szamar", "birka" vagy más.
    /* JANI.C: A KORNYVALT kornyezeti valtozo a megadott ertekek egyike,
               vagy mas, vagy nincs is? (getenv-es valtozat) */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #define KORNYVALT "JANI"
    char *ertekek[]= {"lo","szamar","birka"};
    #define IG sizeof(ertekek)/sizeof(char*)
    int main(void) {
      int i;
      char *ertek;
      printf("A(z) %s kornyezeti valtozo ertekenek lekerdezese\n\n", KORNYVALT);
      if(ertek=getenv(KORNYVALT)) {
        for(i=0; i<IG; i++)
          if(!strcmp(ertek, ertekek[i])) {
            printf("A(z) %s %s!\n", KORNYVALT, ertekek[i]);
            return EXIT_SUCCESS; }
        printf("A(z) %s mas!",KORNYVALT);
        return EXIT_SUCCESS; }
      printf("Nincs is %s!", KORNYVALT);
      return EXIT_FAILURE; }
| 5. Készítsen programot két mátrix összeadására! Az összeadandó mátrixoknak ne, de az eredménymátrixnak dinamikusan foglaljon helyet a memóriában! Az összeadandó mátrixok mérete csak futás időben válik konkréttá. A programban használjon függvényeket az összeadandó mátrixok méretének és elemeinek bekéréséhez, valamint a két mátrix összeadásához!
    /*  MATRIXOS.C - Ket matrix osszeadasa */
    #include <stdio.h>
    #include <stdlib.h>
    #define MERET 10
    int getline(char *s, int lim) {
      int c;
      char *t=s;
      while(lim-->0&&(c=getchar())!=EOF&&c!='\n')*s++=c;
      *s='\0';
      while(c!=EOF&&c!='\n') getchar();
      return(s-t); }
    #define MAX 15
    int meret(char* uzenet) {
      int i=0;
      char buf[MAX+1];
      while(i<1||i>MERET) {
        printf("%-8s szama (1-%d)? ", uzenet, MERET);
        getline(buf, MAX);
        i = atoi(buf); }
      return i; }
    void beolvas(int (*M)[MERET], int o, int s, char* uzenet) {
      int i, j;
      char buf[MAX+1];
      printf("Kerem %s matrix elemeit a megadott sorrendben!\n", uzenet);
      for(i=0; i<o; ++i) {
        for(j=0; j<s; ++j) {
          printf("[%d][%d] : ",i+1,j+1);
          getline(buf, MAX);
          M[i][j] = atoi(buf); } } }
    long (*osszead(int (*A)[MERET], int B[][MERET], int s, int o))[MERET] {
      long (*C)[MERET];
      int i, j;
      if(!(C = (long(*)[MERET])malloc(s*sizeof(long*)))) {
        printf("Nem tudtam memoriat foglalni a matrixnak.\n");
        exit(1); }
      for(i=0; i<s; i++)
        for(j=0; j<o; j++)
            C[i][j] = (long)*(*(A+i)+j) + B[i][j];
      return C; }
    void main(void) {
      int i,j,     /* Indexek */
          s=0,o=0, /* Sorok, oszlopok szama */
          A[MERET][MERET], B[MERET][MERET]; /* A matrixok */
          long (*C)[MERET]; /* Az eredmeny matrix */
      printf("Ket matrix osszeadasa:\n");
      s = meret("Sorok");
      o = meret("Oszlopok");
      beolvas(A, s, o, "az elso");
      beolvas(B, s, o, "a masodik");
      C = osszead(A, B, s, o);
      printf("\nAz osszegmatrix:\n\n");
      for(i=0; i<s; ++i) {
        for(j=0; j<o; ++j)
          printf("[%2d][%2d]:%6ld ", i+1, j+1, C[i][j]);
        putchar('\n'); }
      free(C); }
| 6. Készítsen programot, mely neveket olvas a szabvány bemenetről! Egy sorban egy név érkezik, s az üres sor a bemenet végét jelzi. A név nagybetűvel kezdődik, és a többi karaktere kisbetű. A feltételeket ki nem elégítő név helyett azonnal másikat kell kérni a probléma kijelzése után! A neveket rendezze névsorba, s listázza ki őket lapokra bontva!
    /* NEVREND.C: Beolvasott, dinamikusan mutatotombben tarolt
                  nevek rendezese. */
    #include <stdio.h>
    #include <string.h> /* A karakterlanc-kezeles miatt! */
    #include <stdlib.h> /* A dinamikus tarolas miatt! */
    #include <ctype.h> /* A karaktervizsgalatok miatt! */
    #define SOROK 100 /* A tarolhato nevek max.szama. */
    #define LAPSOR 20 /* Egy lapra irhato nevek szama. */
    int nevekbe(char *nevmutomb[], int maxnevdb);
    void rendez(char *t[], int n);
    void main(void) {
      char *nevmutomb[SOROK]; /* Neveket megcimzo mutatotomb.*/
      int nevdb; /* A beolvasott nevek szama. */
      printf("A beolvasott nevek rendezese:\n");
      printf("A neveket Enter-rel kell lezarni!\n");
      printf("A bemenet veget ures sor jelzi!\n\n");
      if((nevdb=nevekbe(nevmutomb, SOROK))>=0) { /* Beolvasas. */
        int i;
        rendez(nevmutomb, nevdb); /* Rendezes. */
        printf("\n");
        for(i=0; i<nevdb; ++i) { /* Kiiras lapozva. */
          if(i%LAPSOR==0&&i!=0) {
            printf("A tovabblistazashoz usson Enter-t! ");
            while(getchar()!='\n');
            printf("\n\n"); }
          printf("%s\n", nevmutomb[i]); }
      } else printf("\nTul sok nev, vagy elfogyott a "
                    "memoria!\n"); }
    #define MAXNEV 40 /* Egy nev max. hosszusaga */
    int getline(char *, int);
    int nevekbe(char *nevmutomb[], int maxnevdb) {
      /* Nevek beolvasasa
      es dinamikus elhelyezese a parameter mutatotombben
      es a letarolt nevek szamanak visszaadasa. -1-et szolgaltat,
      ha kimerul a mutatotomb, vagy a heap elfogy. */
      int hsz, /* A beolvasott nev hossza. */
          nevdb=0; /* Az eddig beolvasott nevek szama. */
      char *p, /* A dinamikus terulet cime. */
           nev[MAXNEV+1];/* Az aktualisan beolvasott nev. */
      while((hsz=getline(nev, MAXNEV))>0) {
        /* Nev ellenorzese: Elso nagy, a tobbi kisbetű. */
        p=nev+1;
        while(*p&& islower(*p++));
        if(isupper(*nev)&&!(*p)) {
          /* Kimerult-e mutatotomb? */
          if(nevdb>=maxnevdb) return(-1);
          /* Van-e meg memoria? */
          else if((p=(char *)malloc(hsz+1))==NULL) return(-1);
          /* Minden OK: nev a heapre, cime a mutatotombbe. */
          else {
            strcpy(p, nev);
            nevmutomb[nevdb++]=p; }
        } else printf("Ervenytelen nev!\n"); }
      return(nevdb); }
    int getline(char *s, int n) { /* Sor beolvasasa s-be. */
      int c; /* A hosszat adja vissza. */
      char *t=s; /* Indexeles helyett a t mutatoval
        haladunk az s karaktertombon. A t mindig a kovetkezo
        szabad helyre mutat a tombben, melynek tulirasat az n
        elfogyasa akadalyozza meg. */
      while(n-->0&&(c=getchar())!=EOF&&c!='\n') *t++=c;
      *t='\0';
      while(c!=EOF&&c!='\n') c=getchar();
      /* A karakterlacot zaro '\0' cimebol a tomb kezdocimet
         kivonva, eppen a karakterlanc hosszat kapjuk. */
      return(t-s); }
    void rendez(char *v[], int n) { /* A v[0],v[1],..,v[n-1] */
      int i, j, m; /* karakterlancok rendezese novekvo */
      char *cs; /* sorrendben a mutatok cserejevel. */
      for(i=0; i<n-1; ++i) {
        for(j=i+1, m=i; j<n; ++j) if(strcmp(v[j],v[m])<0) m=j;
        if(i!=m) {
          cs=v[i];
          v[i]=v[m];
          v[m]=cs; } } }